/**
 * @particulars 对数据进行观测劫持
 */

import newArrayProto from "./arrayMethods";
import Dep from "./dep";

// 2、Observer 对所有的对象属性进行观测劫持
class Observer {
  constructor(data) {
    // 2-1、给每个对象也增加依赖收集
    this.dep = new Dep();

    // 2-2、给数据添加一个标识，如果数据上有__ob__ 这个属性，说明这个属性被监测劫持过了（data.__ob__ = this => 死循环）
    Object.defineProperty(data, '__ob__', {
      value: this, // __ob__ 指向Observer 的实例，这样对象的__ob__ 身上就会挂载着walk、observerArray 等方法
      enumerable: false // 将__ob__ 变成不可枚举（循环时无法被获取，不然会死循环）
    })

    // 2-3、对于数组，重写七个可以修改数组本身的原型方法，并劫持数组内的对象；对于对象，则直接遍历劫持
    if (Array.isArray(data)) {
      // 为数组对象添加一个__proto__（隐式原型），这样使用变异方法的时候会从新加的原型上读取被重写的方法，同时保留了数组原有的所有方法
      data.__proto__ = newArrayProto;
      // 对数组元素进行劫持
      this.observerArray(data);
    } else {
      this.walk(data);
    }
  }

  // 循环对象属性，对属性依次劫持
  walk(data) {
    Object.keys(data).forEach(key => defineReactive(data, key, data[key]));
  }

  // 对数组的每项成员进行劫持，元素非对象并不会被劫持
  observerArray(data) {
    data.forEach(item => observer(item));
  }
}

// 4、对数组的每一项进行依赖收集
function dependArray(value) {
  for (let i = 0; i < value.length; i++) {
    const currentValue = value[i];
    // currentValue.__ob__ 是被劫持后的数据本身，其挂载的dep 是为每个劫持属性添加的Dep 依赖收集，depend() 会将该属性进行依赖收集，建立对应的依赖关系
    currentValue.__ob__ && currentValue.__ob__.dep.depend();

    // 元素是数组则继续递归依赖收集
    if (Array.isArray(currentValue)) dependArray(currentValue);
  }
}

// 3、劫持对象属性，成为响应式数据
function defineReactive(target, key, value) {
  // 3-1、对劫持对象里面的所有对象进行递归劫持，childOb.dep 用来收集依赖
  const childOb = observer(value);

  // 3-2、给每一个属性添加一个dep
  const dep = new Dep();

  Object.defineProperty(target, key, {
    get() {
      if (Dep.target) {
        // 3-3、让这个属性的收集器Dep 记住当前的Watcher
        dep.depend();
        // 3-4、让数组和对象本身也实现依赖收集，直接修改劫持对象内的对象（obj）也需要响应式 {data: {obj: {age: 123}}}
        if (childOb) {
          childOb.dep.depend();
          // 3-5、如果数组里面还是数组则继续依赖收集
          if (Array.isArray(value)) dependArray(value);
        }
      }

      return value;
    },
    set(newValue) {
      if (newValue === value) return;
      // 设置的值为对象时，同样需要劫持
      observer(newValue);
      value = newValue;
      // 数据改变通知Watcher 更新，实现响应式更新
      dep.notify();
    }
  })
}

// 1、对数据进行观测劫持
export function observer(data) {
  // 只劫持对象的属性，非对象不劫持（针对于数组元素）
  if (typeof data !== 'object' || data === null) return;

  // 如果经过Observer 添加了__ob__，说明对象被劫持过了，直接返回，不需要再监测
  if (data.__ob__ instanceof Observer) return data.__ob__;

  // 返回劫持后的对象
  return new Observer(data);
}